package tech.dnf.tripartite.nnfpjss.executor;

import com.sun.istack.internal.NotNull;
import lombok.experimental.UtilityClass;
import tech.dnf.tripartite.nnfpjss.bean.dto.Request;
import tech.dnf.tripartite.nnfpjss.bean.enums.ERequestOperater;
import tech.dnf.tripartite.nnfpjss.bean.vo.Response;
import tech.dnf.tripartite.nnfpjss.plugin.InterceptorChain;

import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

@UtilityClass
public final class Operaters {

    private InterceptorChain interceptorChain = new InterceptorChain();
    private Map<String, Class<? extends Request>> requestTypes = new ConcurrentHashMap<>();
    private Map<String, Operater<? extends Request>> requestHandlers = new ConcurrentHashMap<>();

    private void preInit(){
        registerOperater(ERequestOperater.QUERY.name, null);
        registerOperater(ERequestOperater.CALLBACK.name, null);
    }

    public void config() {
        preInit();
    }

    public Response execute(Map<String, Object> params) {
        if (Objects.nonNull(params)) {
            String operater = (String) params.get("operater");
            return execute(operater, params);
        }
        return null;
    }

    public <T extends Request> Response execute(String operater, Map<String, Object> params) {
        Class<T> requestType = getRequestType(operater);
        Map<String, Object> requestParam = new HashMap<>();
        requestParam.putAll(params);
        requestParam.put("operater", operater);
        T request = null;
        return execute(request);
    }

    public <T extends Request> Response execute(T request) {
        String operater = request.getOperater();
        Operater<T> handler = getOperaterHandler(operater);
        Response resp = handler.execute(request);
        return resp;
    }

    public <T extends Request> void registerOperater(String operater, Operater<T> handler) {
        registerOperater(operater, handler, false);
    }

    public <T extends Request> void registerOperater(String operater, Operater<T> handler, boolean force) {
        Objects.requireNonNull(operater, "register operater fail, case the operater can't be null");
        Objects.requireNonNull(handler,  ()->String.format("register operater(%s) fail, case the handler can't be null",operater));

        Class<T> requestType = getRequestType(handler);
        if (force) {
            requestHandlers.put(operater, handler);
            registerRequestType(operater, requestType);
        } else {
            Operater oldHandler = requestHandlers.putIfAbsent(operater, handler);
            if (!Objects.equals(oldHandler, handler)) {
                assert oldHandler != null;
                throw new IllegalArgumentException(String.format("register handler(%s) fail for operater(%s), case the handler(%s) is already register",
                    handler.getClass().getName(),
                    operater,
                    oldHandler.getClass().getName()));
            }
            registerRequestType(operater, requestType, true);
        }
    }

    public void registerRequestType(String operater, Class<? extends Request> type) {
        registerRequestType(operater, type, false);
    }

    public void registerRequestType(String operater, Class<? extends Request> type, boolean force) {
        Objects.requireNonNull(operater, "register request type fail, case the operater type can't be null");
        Objects.requireNonNull(type,  ()->String.format("register Operater(%s) request type fail, case the type can't be null",operater));

        if (force) {
            requestTypes.put(operater, type);
        } else {
            Class oldType = requestTypes.putIfAbsent(operater, type);
            if (!Objects.equals(oldType, type)) {
                assert oldType != null;
                throw new IllegalArgumentException(String.format("register type(%s) fail for operater(%s), case the type(%s) is already register",
                    type.getName(),
                    operater,
                    oldType.getName()));
            }
        }
    }

    @NotNull
    public Operater getOperaterHandler(String operater) {
        Objects.requireNonNull(operater, "operater can't be null");

        Operater handlerType = requestHandlers.get(operater);
        if (null == handlerType) {
            throw new IllegalUnregisterException("the request handler not register for " + operater);
        }
        return handlerType;
    }

    public Class getRequestType(String operater) {
        Objects.requireNonNull(operater, "operater can't be null");

        Class requestType = requestTypes.get(operater);
        if (null == requestType) {
            throw new IllegalUnregisterException("the request type not register for " + operater);
        }
        return requestType;
    }

    @SuppressWarnings("unchecked")
    private <T extends Request> Class<T> getRequestType(Operater<T> handler) {
        return (Class<T>) Optional
            .ofNullable(handler)
            .map(Object::getClass)
            .map(Class::getGenericSuperclass)
            .map(type -> (ParameterizedType) type)
            .map(ParameterizedType::getActualTypeArguments)
            .map(types -> types[0])
            .orElse(null);
    }
}
